package com.lcy.algorithm.huawei;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 题目描述:
 * 请设计一个文件缓存系统,该文件缓存系统可以指定缓存的最大值(单位为字节)。
 * 文件缓存系统有两种操作:存储文件(put)和读取文件(get)
 * 操作命令为put fileName fileSize或者get fileName
 * 存储文件是把文件放入文件缓存系统中;读取文件是从文件缓存系统中访问已存在的文件,如果文件不存在，则不作任何操作。
 * 当缓存空间不足以存放新的文件时,根据规则删除文件，直到剩余空间满足新的文件大小为止,再存放新文件。
 * 具体地删除规则为:
 * 文件访问过后，会更新文件的最近访问时间和总的访问次数,当缓存不够时,按照第一优先顺序为访问次数从少到多 ,第二顺序为时间从老到新的方式来删除文
 * 件。
 * 输入描述:第一行为缓存最大值m(整数，取值范围为0 < m <= 52428800) ;
 * 第二行为文件操作序列个数n(0 <= n <= 300000
 * 从第三行起为文件操作序列,每个序列单独- -行
 * 文件操作定义为"op file_ namefile_ size"
 * file_ name是文件名，file. size是文件大小
 * 输出描述:输出当前文件缓存中的文件名列表，文件名用英文逗号分隔,按字典顺序排序
 * 如:
 * a,c
 * 如果文件缓存中没有文件,则输出NONE
 * 补充说明: 1.如果新文件的文件名和文件缓存中已有的文件名相同,则不会放在缓存中
 * 2.新的文件第一次存入到文件缓存中时 ，文件的总访问次数不会变化,文件的最近访问时间会更新到最新时间
 * 3.每次文件访问后，总访问次数加1 ,最近访问时间更新到最新时间
 * 4.任何两个文件的最近访问时间不会重复
 * 5.文件名不会为空,均为小写字母,最大长度为10。
 * 6.缓存空间不足时,不能存放新文件
 * 7.每个文件大小都是大于0的整数
 *
 * 示例1
 * 输入:
 * 50
 * 6
 * put a 10
 * put b 20
 * get a
 * get a
 * get b
 * put c 30
 * 输出: a,c
 * 说明:
 * 示例2
 * 输入:
 * 50
 * 1
 * get file
 * 输出: NONE
 *
 */
public class 文件缓存系统 {
    private static final int MAX_CACHE_SIZE = 52428800; // 最大缓存大小，单位为字节
    private static AtomicInteger CACHE_SIZE = new AtomicInteger(0);
    private static AtomicInteger currentCacheSize = new AtomicInteger(0);; // 当前缓存大小
    private static ConcurrentHashMap<String, FileInfo> cacheMap = new ConcurrentHashMap<>(); // 文件缓存映射，用于存储文件信息

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int maxCacheSize = scanner.nextInt(); // 缓存最大值
        scanner.nextLine(); // 消耗换行符

        int sequence = scanner.nextInt(); //文件操作序列
        scanner.nextLine(); // 消耗换行符

        if (maxCacheSize > 0 && maxCacheSize <= MAX_CACHE_SIZE) {
            CACHE_SIZE.set(maxCacheSize);
            for(int i = 0; i < sequence; i ++ ){
                String command = scanner.nextLine();
                processCommand(command);
            }
            printCachedFiles();
        } else {
            System.out.println("缓存最大值超出范围");
        }
    }

    private static void processCommand(String command) {
        String[] tokens = command.split(" ");
        if (tokens.length < 2 || tokens.length > 3) {
            System.out.println("命令格式错误：" + command);
            return;
        }

        String op = tokens[0];
        String fileName = tokens[1];
        switch (op) {
            case "put":
                if (tokens.length >= 3) {
                    int fileSize = Integer.parseInt(tokens[2]);
                    putFile(fileName, fileSize);
                } else {
                    System.out.println("存储文件命令格式错误：" + command);
                }
                break;
            case "get":
                getFile(fileName);
                break;
            default:
                System.out.println("未知命令：" + op);
        }
    }

    private synchronized static void putFile(String fileName, int fileSize) {
        if (fileSize > CACHE_SIZE.get() || cacheMap.containsKey(fileName)) {
            return; // 文件大小超出缓存或文件已存在，不进行存储
        }

        while (currentCacheSize.get() + fileSize > CACHE_SIZE.get()) {
            deleteFile(); // 缓存空间不足时按规则删除文件
        }

        FileInfo fileInfo = new FileInfo(fileName, fileSize);
        cacheMap.put(fileName, fileInfo);
        currentCacheSize.addAndGet(fileSize);
    }

    private synchronized static void getFile(String fileName) {
        FileInfo fileInfo = cacheMap.get(fileName);
        if (fileInfo != null) {
            fileInfo.accessFile(); // 访问文件，更新访问时间和访问次数
        }
    }

    private synchronized static void deleteFile() {
        if (cacheMap.isEmpty()) {
            return;
        }

        List<Map.Entry<String, FileInfo>> entries = new ArrayList<>(cacheMap.entrySet());
        // 按访问次数从少到多排序，如果访问次数相同则按最近访问时间从老到新排序
        Collections.sort(entries, Comparator.comparingInt((Map.Entry<String, FileInfo> entry) -> entry.getValue().accessCount)
                .thenComparingLong(entry -> entry.getValue().lastAccessTime));

        Map.Entry<String, FileInfo> oldestEntry = entries.get(0);
        currentCacheSize.addAndGet(-oldestEntry.getValue().fileSize);
        cacheMap.remove(oldestEntry.getKey());
    }

    private static void printCachedFiles() {
        List<String> cachedFiles = new ArrayList<>(cacheMap.keySet());
        Collections.sort(cachedFiles);
        if (cachedFiles.isEmpty()) {
            System.out.println("NONE");
        } else {
            System.out.println(String.join(",", cachedFiles));
        }
    }

    static class FileInfo {
        String fileName;
        int fileSize;
        int accessCount;
        long lastAccessTime;

        FileInfo(String fileName, int fileSize) {
            this.fileName = fileName;
            this.fileSize = fileSize;
            this.accessCount = 0;
            this.lastAccessTime = System.currentTimeMillis();
        }

        void accessFile() {
            accessCount++;
            lastAccessTime = System.currentTimeMillis();
        }
    }
}
